/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.webservicecomponent.dt.core.convertor;

import com.inspur.edp.caf.cef.schema.base.datatype.impl.DefaultCollectionType;
import com.inspur.edp.caf.cef.schema.base.datatype.impl.DefaultMapType;
import com.inspur.edp.caf.cef.schema.base.datatype.impl.DefaultPrimitiveType;
import com.inspur.edp.caf.cef.schema.base.element.impl.DefaultParameter;
import com.inspur.edp.caf.cef.schema.datatype.DataType;
import com.inspur.edp.caf.cef.schema.datatype.ObjectType;
import com.inspur.edp.caf.cef.schema.datatype.PrimitiveTypeKind;
import com.inspur.edp.caf.cef.schema.element.Parameter;
import com.inspur.edp.component.schema.RefStructureInfo;
import com.inspur.edp.component.schema.StructuredTypeRefer;
import com.inspur.edp.component.schema.resource.IApiResource;
import com.inspur.edp.internalservice.api.entity.GspIapiMetadata;
import com.inspur.edp.internalservice.api.entity.IapiOperation;
import com.inspur.edp.internalservice.api.entity.definition.RefStructureType;
import com.inspur.edp.internalservice.api.entity.obsolete.InternalApiMetadata;
import com.inspur.edp.internalservice.api.entity.obsolete.ServiceOperation;
import com.inspur.edp.internalservice.api.entity.obsolete.ServiceParameter;
import com.inspur.edp.lcm.metadata.api.IMetadataContent;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.lcm.metadata.api.service.RefCommonService;
import com.inspur.edp.webservicecomponent.metadata.WebServiceComponent;
import com.inspur.edp.webservicecomponent.metadata.WebServiceOperation;
import com.inspur.edp.webservicecomponent.metadata.parameter.WebServiceParameter;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import org.apache.commons.lang3.StringUtils;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @ClassName: SgIapiConvertor
 * @Author: Fynn Qi
 * @Date: 2020/12/30 11:30
 * @Version: V1.0
 */
public class SgIapiConvertor {

    private static final String GSPBUSINESSENTITY = "GSPBusinessEntity";
    private static final String UNIFIED_DATA_TYPE = "UnifiedDataType";
    private static final String GSPVIEWMODEL = "GSPViewModel";
    private static final String COMMON_DATA_TYPE = "CommonDataType";

    private InternalApiMetadata obsoleteIapiMetadata;
    private GspIapiMetadata iapiMetadata;
    private ServiceOperation obsoleteCurrentOperation;
    private IapiOperation currentOperation;
    private List<RefStructureInfo> refStructures;

    public static SgIapiConvertor instance;

    static {
        instance = new SgIapiConvertor();
    }

    public WebServiceComponent convertor(String metadataId) {
        WebServiceComponent cmp = new WebServiceComponent();
        this.refStructures = cmp.getRefStructures();//用于设置引用结构
        IMetadataContent iapiContent = getIapiMetadataContent(metadataId);
        //如果不为空，则是旧结构
        if (iapiContent instanceof InternalApiMetadata) {
            this.obsoleteIapiMetadata = (InternalApiMetadata) iapiContent;
            //基本信息
            obsoleteIapiBaseInfoConvertor(obsoleteIapiMetadata, cmp);
            //操作信息
            obsoleteIapiOperationConvertor(obsoleteIapiMetadata, cmp);
        } else {
            this.iapiMetadata = (GspIapiMetadata) iapiContent;
            //基本信息
            newIapiBaseInfoConvertor(iapiMetadata, cmp);
            //操作信息
            newIapiOperationConvertor(iapiMetadata, cmp);
        }
        return cmp;
    }

    private void newIapiOperationConvertor(GspIapiMetadata iapi, WebServiceComponent cmp) {
        if (iapi.getDefinition() == null || iapi.getDefinition().getOperations() == null || iapi.getDefinition().getOperations().size() <= 0) {
            return;
        }
        //cmp.setOperations(new ArrayList<>());
        for (IapiOperation op : iapi.getDefinition().getOperations()) {
            this.currentOperation = op;
            WebServiceOperation cmpOperation = new WebServiceOperation();
            newIapiOperationBaseInfoConvertor(op, cmpOperation);
            cmpOperation.setReturnType(this.getNewIapiDataType(op.getReturnType()));
            if (op.getParameters() == null || op.getParameters().size() <= 0) {
                continue;
            }
            newIapiParameterConvertor(op, cmpOperation);
            this.tryAddNewIapiOperation(cmp.getOperations(), cmpOperation);
        }
    }

    private void obsoleteIapiOperationConvertor(InternalApiMetadata iapi, WebServiceComponent cmp) {
        if (iapi.getService() == null || iapi.getService().getOperations() == null || iapi.getService().getOperations().isEmpty()) {
            return;
        }
        for (ServiceOperation op : iapi.getService().getOperations()) {
            this.obsoleteCurrentOperation = op;
            WebServiceOperation cmpOperation = new WebServiceOperation();
            operationBaseInfoConvertor(op, cmpOperation);
            if (op.getParameters() == null || op.getParameters().size() <= 0) {
                continue;
            }
            parameterConvertor(op, cmpOperation);
            this.tryAddOperation(cmp.getOperations(), cmpOperation);
        }
    }

    private void newIapiParameterConvertor(IapiOperation iapiOp, WebServiceOperation cmpOperation) {
        cmpOperation.setParameters(new ArrayList<>());
        for (Parameter param : iapiOp.getParameters()) {
            WebServiceParameter cmpParameter = getNewIapiCmpParam((DefaultParameter) param);
            this.tryAddNewIapiParam(cmpOperation.getCompParameters(), cmpParameter);
        }
    }

    private void parameterConvertor(ServiceOperation op, WebServiceOperation cmpOperation) {
        cmpOperation.setParameters(new ArrayList<>());
        for (ServiceParameter param : op.getParameters()) {
            if (param.isRetVal()) {
                continue;
            }
            WebServiceParameter cmpParameter = getCmpParam(param);
            this.tryAddParam(cmpOperation.getCompParameters(), cmpParameter);
        }

        boolean existReturnType = op.getParameters().stream().anyMatch(ServiceParameter::isRetVal);
        if (existReturnType) {
            ServiceParameter retParam = op.getParameters().stream().filter(ServiceParameter::isRetVal).collect(Collectors.toList()).get(0);
            cmpOperation.setReturnType(getParamType(retParam));
        }
    }

    private WebServiceParameter getNewIapiCmpParam(DefaultParameter param) {
        WebServiceParameter cmpParameter = new WebServiceParameter();
        cmpParameter.setCode(param.getCode());
        cmpParameter.setName(param.getName());
        cmpParameter.setDescription(param.getDescription());
        cmpParameter.setRequired(param.isRequired());
        cmpParameter.setParameterType(this.getNewIapiDataType(param.getParameterType()));
        return cmpParameter;
    }

    private DataType getNewIapiDataType(DataType dataType) {
        if (dataType == null) {
            return null;
        }
        String kind = dataType.getKind();
        if (StringUtils.isBlank(kind)) {
            throw new RuntimeException("内部服务操作参数转换为Webservice构件参数失败:数据类型Kind为空。");
        }
        switch (kind) {
            case DefaultPrimitiveType.KIND:
                return dataType;
            case DefaultCollectionType.KIND:
                DataType elementType = ((DefaultCollectionType) dataType).getElementType();
                return this.getNewIapiDataType(elementType);
            case DefaultMapType.KIND:
                DataType keyType = ((DefaultMapType) dataType).getKeyType();
                DataType valueType = ((DefaultMapType) dataType).getValueType();
                DefaultMapType result = new DefaultMapType();
                result.setKeyType(this.getNewIapiDataType(keyType));
                result.setValueType(this.getNewIapiDataType(valueType));
                return result;
            case RefStructureType.KIND:
                RefStructureType type = (RefStructureType) dataType;
                StructuredTypeRefer wsCmpType = convertIapiDataTypeToWsCmpDataType(type);
                RefStructureInfo refInfo = new RefStructureInfo();
                convertIapiRefTypeToWsCmpRefInfo(refInfo, type);
                this.tryAddRefStructureInfo(this.refStructures, refInfo);
                return wsCmpType;
            case ObjectType.KIND:
                return dataType;
            default:
                throw new RuntimeException("不支持的内部服务数据类型:{kind}");
        }
    }

    private static StructuredTypeRefer convertIapiDataTypeToWsCmpDataType(RefStructureType type) {
        StructuredTypeRefer wsCmpType = new StructuredTypeRefer();
        wsCmpType.setReferId(type.getId());
        wsCmpType.setCode(type.getCode());
        wsCmpType.setName(type.getName());
        return wsCmpType;
    }

    private static void convertIapiRefTypeToWsCmpRefInfo(RefStructureInfo refInfo, RefStructureType type) {
        refInfo.setId(type.getId());
        refInfo.setCode(type.getCode());
        refInfo.setName(type.getName());
        refInfo.setReferId(type.getRefId());
        refInfo.setReferCode(type.getRefCode());
        refInfo.setReferType(type.getRefType());
        refInfo.setStructuredTypeId(type.getId());
    }

    private WebServiceParameter getCmpParam(ServiceParameter param) {
        WebServiceParameter cmpParameter = new WebServiceParameter();
        cmpParameter.setCode(param.getCode());
        cmpParameter.setName(param.getName());
        cmpParameter.setDescription(param.getDescription());
        cmpParameter.setRequired(param.isRequire());
        cmpParameter.setParameterType(getParamType(param));
        return cmpParameter;
    }

    private DataType getParamType(ServiceParameter param) {
        //基本类型
        if (param.isBasic()) {
            //列表
            if (param.isCollection()) {
                DefaultCollectionType type = new DefaultCollectionType();
                type.setElementType(getBasicType(param));
                return type;
            }
            //非列表
            else {
                return getBasicType(param);
            }
        }
        //非基本类型
        else {
            if (param.isCollection()) {
                DefaultCollectionType type = new DefaultCollectionType();
                type.setElementType(this.getRefType(param));
                return type;
            } else {
                return this.getRefType(param);
            }
        }
    }

    private DataType getRefType(ServiceParameter param) {
        switch (param.getFieldType()) {
            case Basic:
                return getBasicType(param);
            case BE:
                return getParamRefInfo(param, GSPBUSINESSENTITY);
            case Cdt:
                return getParamRefInfo(param, COMMON_DATA_TYPE);
            case ChangeSet:
                return new ObjectType();
            case EntityFilter:
                return new ObjectType();
            case Object:
                return new ObjectType();
            case Udt:
                return getParamRefInfo(param, UNIFIED_DATA_TYPE);
            case VO:
                return getParamRefInfo(param, GSPVIEWMODEL);
            default:
                return new ObjectType();
        }
    }

    private DataType getParamRefInfo(ServiceParameter param, String metadataType) {
        RefStructureInfo refInfo = getRefStructureInfo(param, metadataType);
        this.tryAddRefStructureInfo(this.refStructures, refInfo);
        return getStructuredTypeRefer(refInfo);
    }

    private static StructuredTypeRefer getStructuredTypeRefer(RefStructureInfo refInfo) {
        StructuredTypeRefer refType = new StructuredTypeRefer();
        refType.setReferId(refInfo.getStructuredTypeId());
        refType.setCode(refInfo.getCode());
        refType.setName(refInfo.getName());
        return refType;
    }

    private RefStructureInfo getRefStructureInfo(ServiceParameter param, String metadataType) {
        RefStructureInfo refInfo = new RefStructureInfo();
        refInfo.setId(param.getRefTypeId());
        refInfo.setCode(param.getRefTypeCode());
        refInfo.setName(param.getRefTypeName());
        refInfo.setStructuredTypeId(param.getRefTypeId());
        refInfo.setReferId(param.getRefId());
        refInfo.setReferCode(param.getRefCode());
        refInfo.setReferType(metadataType);
        return refInfo;
    }

    private static DataType getBasicType(ServiceParameter param) {
        switch (param.getBasicType()) {
            case String:
                return getPrimitiveType(PrimitiveTypeKind.String);
            case Text:
                return getPrimitiveType(PrimitiveTypeKind.String);
            case Integer:
                return getPrimitiveType(PrimitiveTypeKind.Int32);
            case Decimal:
                return getPrimitiveType(PrimitiveTypeKind.Decimal);
            case Boolean:
                return getPrimitiveType(PrimitiveTypeKind.Bool);
            case Date:
                return getPrimitiveType(PrimitiveTypeKind.DateTime);
            case DateTime:
                return getPrimitiveType(PrimitiveTypeKind.DateTime);
            case Binary:
                return getPrimitiveType(PrimitiveTypeKind.Byte);
            case UnKnow:
                return getObjectType();
            default:
                return getObjectType();
        }
    }

    private static DefaultPrimitiveType getPrimitiveType(PrimitiveTypeKind primitiveTypeKind) {
        DefaultPrimitiveType type = new DefaultPrimitiveType();
        type.setPrimitiveTypeKind(primitiveTypeKind);
        return type;
    }

    private static ObjectType getObjectType() {
        return new ObjectType();
    }

    private static void newIapiOperationBaseInfoConvertor(IapiOperation op, WebServiceOperation cmpOperation) {
        cmpOperation.setId(op.getId());
        cmpOperation.setUri(op.getId());//构件之前采用DefaultOperation，无id字段，故放在uri上
        cmpOperation.setCode(op.getCode());
        cmpOperation.setName(op.getName());
        cmpOperation.setDescription(op.getDescription());
    }

    private static void operationBaseInfoConvertor(ServiceOperation op, WebServiceOperation cmpOperation) {
        cmpOperation.setId(op.getId());
        cmpOperation.setUri(op.getId());//构件之前采用DefaultOperation，无id字段，所以放在uri上
        cmpOperation.setCode(op.getCode());
        cmpOperation.setName(op.getName());
        cmpOperation.setDescription(op.getDescription());
    }

    private static void obsoleteIapiBaseInfoConvertor(InternalApiMetadata iapi, WebServiceComponent cmp) {
        cmp.setId(iapi.getId());
        cmp.setCode(iapi.getCode());
        cmp.setName(iapi.getName());
        cmp.setDescription(iapi.getDescription());
        cmp.setApplication(iapi.getApplication());
        cmp.setServiceUnit(iapi.getServiceUnit());
        cmp.setVersion(iapi.getVersion());
        cmp.setResource(getSourceInfo(iapi));
    }

    private static void newIapiBaseInfoConvertor(GspIapiMetadata iapi, WebServiceComponent cmp) {
        cmp.setId(iapi.getId());
        cmp.setCode(iapi.getCode());
        cmp.setName(iapi.getName());
        cmp.setDescription(iapi.getDescription());
        cmp.setApplication(iapi.getApplication());
        cmp.setServiceUnit(iapi.getServiceUnit());
        cmp.setVersion(iapi.getVersion());
        cmp.setResource(getNewIapiSourceInfo(iapi));
    }

    private static IApiResource getNewIapiSourceInfo(GspIapiMetadata iapi) {
        IApiResource sourceInfo = new IApiResource();
        sourceInfo.setApiId(iapi.getId());
        sourceInfo.setApiCode(iapi.getCode());
        sourceInfo.setApiName(iapi.getName());
        sourceInfo.setApiServiceUnit(iapi.getServiceUnit());
        sourceInfo.setApiUri(iapi.getUri());
        return sourceInfo;
    }

    private static IApiResource getSourceInfo(InternalApiMetadata iapi) {
        IApiResource sourceInfo = new IApiResource();
        sourceInfo.setApiId(iapi.getId());
        sourceInfo.setApiCode(iapi.getCode());
        sourceInfo.setApiName(iapi.getName());
        sourceInfo.setApiServiceUnit(iapi.getServiceUnit());
        sourceInfo.setApiUri(MessageFormat.format("{0}/{1}/v{2}/{3}", iapi.getApplication(), iapi.getServiceUnit(), iapi.getVersion(), iapi.getRoute()));
        return sourceInfo;
    }

    private IMetadataContent getIapiMetadataContent(String metadataId) {
        RefCommonService refCommon = SpringBeanUtils.getBean(RefCommonService.class);
        GspMetadata metadata = refCommon.getRefMetadata(metadataId);
        return metadata.getContent();
    }

    private void tryAddRefStructureInfo(List<RefStructureInfo> refStructureInfos, RefStructureInfo refStructureInfo) {
        if (refStructureInfos.isEmpty()) {
            refStructureInfos.add(refStructureInfo);
            return;
        }
        boolean exist = refStructureInfos.stream().anyMatch(x -> x.getId().equals(refStructureInfo.getId()));
        if (!exist) {
            refStructureInfos.add(refStructureInfo);
        }
    }

    private void tryAddNewIapiOperation(List<WebServiceOperation> operations, WebServiceOperation operation) {
        if (operations.isEmpty()) {
            operations.add(operation);
            return;
        }
        boolean exist = operations.stream().anyMatch(x -> x.getUri().equals(operation.getUri()));
        if (!exist) {
            operations.add(operation);
        } else {
            throw new RuntimeException(String.format("内部服务%s[ID:%s]中服务操作%s重复！", this.iapiMetadata.getCode(), this.iapiMetadata.getId(), this.currentOperation.getCode()));
        }
    }

    private void tryAddOperation(List<WebServiceOperation> operations, WebServiceOperation operation) {
        if (operations.isEmpty()) {
            operations.add(operation);
            return;
        }
        boolean exist = operations.stream().anyMatch(x -> x.getUri().equals(operation.getUri()));
        if (!exist) {
            operations.add(operation);
        } else {
            throw new RuntimeException(String.format("内部服务%s[ID:{%s]中服务操作%s重复！", this.obsoleteIapiMetadata.getCode(), this.obsoleteIapiMetadata.getId(), this.obsoleteCurrentOperation.getCode()));
        }
    }

    private void tryAddNewIapiParam(List<WebServiceParameter> parameters, WebServiceParameter parameter) {
        if (parameters.isEmpty()) {
            parameters.add(parameter);
            return;
        }
        boolean exist = parameters.stream().anyMatch(x -> x.getCode().equals(parameter.getCode()));
        if (!exist) {
            parameters.add(parameter);
        } else {
            throw new RuntimeException(String.format("内部服务%s[ID:%s]中服务操作%s的参数编号%s重复！", this.iapiMetadata.getCode(), this.iapiMetadata.getId(), this.currentOperation.getCode(), parameter.getCode()));
        }
    }

    private void tryAddParam(List<WebServiceParameter> parameters, WebServiceParameter parameter) {
        if (parameters.isEmpty()) {
            parameters.add(parameter);
            return;
        }
        boolean exist = parameters.stream().anyMatch(x -> x.getCode().equals(parameter.getCode()));
        if (!exist) {
            parameters.add(parameter);
        } else {
            throw new RuntimeException(String.format("内部服务%s[ID:%s]中服务操作%s的参数编号%s重复！", this.obsoleteIapiMetadata.getCode(), this.obsoleteIapiMetadata.getId(), this.obsoleteCurrentOperation.getCode(), parameter.getCode()));
        }
    }
}
